package com.smoed.common.utilities.unitconversions;

public enum TimeUnitConversions {
	NANO(1, 100, "ns"),
	MICRO(1000, 100000, "us"),
	MILLI(1000000, 1000000000, "ms"),
	SECOND(1000000000, 120000000000L, "seconds"),
	MINUTE(60000000000L, 3600000000000L, "minutes"),
	HOUR(3600000000000L, 172800000000000L, "hours"),
	DAY(86400000000000L, 31556926000000000L, "days"),
	YEAR(31556926000000000L, Long.MAX_VALUE, "years");

	private final long numberOfNanoSeconds;
	private final long maxOptimalValue;
	private final String toString;

	private TimeUnitConversions(long numberOfNanoSeconds, long maxOptimalValue, String toString) {
		this.numberOfNanoSeconds = numberOfNanoSeconds;
		this.maxOptimalValue = maxOptimalValue;
		this.toString = toString;
	}

	public long getNumberOfNanoSeconds() {
		return numberOfNanoSeconds;
	}

	public double convertTo(TimeUnitConversions unit, long value) {
		long nanos = getNumberOfNanoSeconds() * value;
		switch(unit) {
		case MICRO:
			return nanos / (double) MICRO.getNumberOfNanoSeconds();
		case MILLI:
			return nanos / (double) MILLI.getNumberOfNanoSeconds();
		case SECOND:
			return nanos / (double) SECOND.getNumberOfNanoSeconds();
		case MINUTE:
			return nanos / (double) MINUTE.getNumberOfNanoSeconds();
		case HOUR:
			return nanos / (double) HOUR.getNumberOfNanoSeconds();
		case DAY:
			return nanos / (double) DAY.getNumberOfNanoSeconds();
		case YEAR:
			return nanos / (double) YEAR.getNumberOfNanoSeconds();
		}
		return nanos;
	}

	public TimeUnitConversions getOptimalUnit(long value) {
		final long nanos = this.getNumberOfNanoSeconds() * value;

		final TimeUnitConversions[] values = TimeUnitConversions.values();

		int low = 0;
		int high = values.length;

		while (low <= high) {
			final int mid = (low + high) >>> 1;
			if (mid == values.length-1 || mid == 0)
				return values[mid];

			final long val = values[mid].maxOptimalValue;
			if (val <= nanos) {
				if (values[mid+1].maxOptimalValue > nanos)
					return values[mid+1];
				low = mid + 1;
			} else if (val > nanos) {
				if (values[mid-1].maxOptimalValue < nanos)
					return values[mid-1];
				high = mid - 1;
			}
		}
		return null;
	}

	public String getConvenientRepresentation(long value) {
		final TimeUnitConversions optimalUnit = this.getOptimalUnit(value);
		final double optimalValRep = this.convertTo(optimalUnit, value);

		return String.format("%,.2f " + optimalUnit.toString, optimalValRep);
	}
}
